mediasoup入門ガイド ― WebRTC SFU を活用したリアルタイム通信の全貌
近年、オンラインミーティングやライブ配信、マルチプレイヤーゲームなど、リアルタイム性が求められるWebアプリが急増しています。その中で WebRTC はブラウザ間の直接通信を実現する重要技術ですが、複数参加者が同時に映像・音声をやり取りする際に課題が出てきます。そこで登場するのが SFU(Selective Forwarding Unit)、そして mediasoup です。
目次
- mediasoup とは何か?
- mediasoup のアーキテクチャ
- 主要機能と特徴
- 代表的なユースケース
- 始め方:インストールからシンプルなコード例まで
- 実装時のベストプラクティス
- さらに学びたい方へ – 参考リソース
mediasoup とは何か?
mediasoup は Node.js で動作するオープンソースの SFU ライブラリです。C++ のコアと JavaScript のラッパーで構成されており、次のような特長があります。
- 高性能かつ低レイテンシ―なメディア転送を実現。
- マルチトラック、マルチストリーム、スケーラブルな「ルーム」設計。
- WebRTC の標準 API にできるだけ忠実なインターフェース。
- プラグイン的に拡張可能で、カスタムルーティングやトランスコーディングも実装可能。
mediasoup のアーキテクチャ
mediasoup の内部は以下のコンポーネントで構成されます。
1. Router
各「会議」や「ルーム」に対応する論理的なハブです。Router は RtpCapabilities(受信できるコーデック情報)や RtpParameters(送信時の設定)を管理し、参加者同士のメディア転送経路を決定します。
2. Transport
実際のデータ転送は WebRtcTransport(UDP/TLSベースの DTLS)か PlainTransport(UDP/TCP の単純な転送)で行われます。各クライアントは自分専用の Transport を作成し、そこに Producer と Consumer を紐付けます。
3. Producer / Consumer
- Producer:ブラウザ側のカメラ・マイクから送られたメディアストリームをサーバに送信。
- Consumer:他のクライアントが送信したストリームを受信し、ブラウザ側に転送。
この 1対N のパターンにより、サーバはメディアを「コピー」せずに効率的にフォワーディングできます。
主要機能と特徴
- マルチコードック対応:VP8/VP9/H.264/AV1 など、主要なビデオコーデックと Opus/AAC など音声コーデックをネイティブでサポート。
- Simulcast & Scalable Video Coding (SVC):帯域に応じた解像度やフレームレートの自動切り替え。
- オーディオレベル・音量測定:音声の活性化検出や音量メーター表示が容易。
- プラグイン可能なビデオエンジン:SFU の前に独自のエンジン(例: AI顔認識、背景合成)を差し込める。
- 細かい帯域制御:各 Consumer に対して
maxSpatialLayerやpreferredBitrateを設定可能。 - 統計取得 API:
getStats()でリアルタイムのパケットロスやジッターを取得し、QoE 改善に活用。
代表的なユースケース
- 大規模オンライン会議(Zoom、Microsoft Teams 風)
1,000 人規模でもRouterとTransportを水平分割し、負荷を分散。 - ライブストリーミング + 双方向チャット
プロデューサー側は高解像度映像、視聴者は低解像度の映像と音声だけを受信し、負荷を最小化。 - ゲーム内ボイスチャット
短い音声パケットを低レイテンシで転送し、リアルタイム性が求められるシーンに最適。 - VR/AR コラボレーション
3D 空間での映像ストリームを simultane で配信し、各ユーザーの視点に合わせて解像度を調整。
始め方:インストールからシンプルなコード例まで
1. 前提条件
- Node.js ≥ 14(LTS 推奨)
- npm または yarn
- サーバは UDP/TCP の 443(TLS) と 4443(非 TLS)あたりのポートが開放されていること
2. インストール
# npm
npm install mediasoup@latest
# yarn
yarn add mediasoup@latest
3. 基本的なサーバ構成例(Node.js)
const http = require('http');
const express = require('express');
const socketIo = require('socket.io');
const mediasoup = require('mediasoup');
const app = express();
const server = http.createServer(app);
const io = socketIo(server);
let worker; // mediasoup Worker
let router; // 1 room 用 Router
(async () => {
// 1️⃣ Worker の起動
worker = await mediasoup.createWorker({
rtcMinPort: 40000,
rtcMaxPort: 40100,
logLevel: 'warn',
});
// 2️⃣ Router の作成(ビデオ/オーディオコーデックを指定)
router = await worker.createRouter({
mediaCodecs: [
{
kind: 'audio',
mimeType: 'audio/opus',
clockRate: 48000,
channels: 2,
},
{
kind: 'video',
mimeType: 'video/VP8',
clockRate: 90000,
parameters: { 'x-google-start-bitrate': 1000 },
},
],
});
// 3️⃣ Socket.io でクライアントとシグナリング
io.on('connection', socket => {
console.log('Client connected:', socket.id);
// クライアントが『トランスポート作成要求』を送る
socket.on('createWebRtcTransport', async ({ forceTcp }, callback) => {
const transport = await router.createWebRtcTransport({
listenIps: [{ ip: '0.0.0.0', announcedIp: 'YOUR_PUBLIC_IP' }],
enableTcp: forceTcp,
enableUdp: !forceTcp,
preferTcp: forceTcp,
maxIncomingBitrate: 1500000,
});
// Transport の情報をクライアントに返す
callback({
id: transport.id,
iceParameters: transport.iceParameters,
iceCandidates: transport.iceCandidates,
dtlsParameters: transport.dtlsParameters,
});
// 以降はクライアントからの DTLS 終了通知を受け取る
socket.on('connectTransport', async ({ dtlsParameters }) => {
await transport.connect({ dtlsParameters });
});
// Producer 登録
socket.on('produce', async ({ kind, rtpParameters }, prodCallback) => {
const producer = await transport.produce({ kind, rtpParameters });
prodCallback({ id: producer.id });
// 他クライアントへ Consumer を作成するロジックは別途実装
});
});
});
// 4️⃣ サーバ起動
const PORT = process.env.PORT || 3000;
server.listen(PORT, () => console.log(`Server listening on ${PORT}`));
})();
4. クライアント側(ブラウザ)サンプル
以下は socket.io-client と標準 WebRTC API を組み合わせた最小構成です。
import io from 'socket.io-client';
const socket = io('https://your-domain.com');
// 1️⃣ メディア取得
const stream = await navigator.mediaDevices.getUserMedia({ video: true, audio: true });
// 2️⃣ Transport 作成リクエスト
socket.emit('createWebRtcTransport', { forceTcp: false }, async (transportInfo) => {
const pc = new RTCPeerConnection({
iceServers: [{ urls: 'stun:stun.l.google.com:19302' }],
});
// ICE & DTLS のセットアップ
transportInfo.iceCandidates.forEach(c => pc.addIceCandidate(c));
pc.setRemoteDescription({ type: 'offer', sdp: transportInfo.iceParameters });
// 3️⃣ Transport 接続
socket.emit('connectTransport', { dtlsParameters: pc.getLocalDescription() });
// 4️⃣ Producer(映像)登録
const videoTrack = stream.getVideoTracks()[0];
const sender = pc.addTrack(videoTrack, stream);
const rtpParameters = sender.getParameters();
socket.emit('produce', { kind: 'video', rtpParameters }, ({ id }) => {
console.log('Producer created with id:', id);
});
});
// 5️⃣ 受信側は別途 Consumer を作成し、pc.addTrack() で受信ストリームを再生
ポイントまとめ
- mediasoup は「サーバ側で転送」だけを行うので、CPU 消費は最小限に抑えられます。
- シグナリングは好きなプロトコル(WebSocket, Socket.io, REST)で実装可能です。
- 実際のサービスでは「部屋管理」「認証」「スケーリング(Kubernetes でワーカープール)」「モニタリング」等が必要です。
実装時のベストプラクティス
1. ワーカーのスケーリング
CPU コア数に合わせて複数の worker を生成し、Router を水平分散させます。Kubernetes の Deployment + HorizontalPodAutoscaler が相性抜群。
2. 帯域・レイヤー制御
- Simulcast/SPATIAL 切替は
consumer.setPreferredLayers()で制御。 - 遠隔ユーザーの回線が不安定な場合は
maxIncomingBitrateを絞ってパケットロスを抑制。
3. 統計情報の活用
定期的に router.getStats() と transport.getStats() を取得し、アラートや自動リカバリロジックを構築。
4. セキュリティ
- 必ず DTLS と SRTP(デフォルト)で暗号化。
- 認証トークン(JWT)で socket 接続を保護し、不要なルームアクセスを防止。
- メディアサーバが公開 IP に直接露出しないよう、ファイアウォールで UDP/443 のみ開放。
5. ログとモニタリング
mediasoup は worker.on('log', …) でログレベルを変更可能。Prometheus exporter や Grafana ダッシュボードを組み合わせると運用が楽です。
さらに学びたい方へ – 参考リソース
- 公式リポジトリ & ドキュメント – mediasoup.org
- GitHub Issues & Discussions – 実装上の疑問はここで検索・質問
- 「WebRTC & mediasoup 入門」動画シリーズ(YouTube) – 実際のコードとデモが豊富
- スケーラビリティ実装例 – mediasoup-demo
- Community Slack – mediasoup Slack
mediasoup は「高度なリアルタイムコミュニケーションを、低コストかつスケーラブルに実装したい」開発者にとって最適な選択肢です。この記事で紹介した概念とサンプルコードを土台に、ぜひ自社サービスや趣味のプロジェクトに組み込んでみてください。