mediasoup入門

—

by

in

mediasoup入門ガイド ― WebRTC SFU を活用したリアルタイム通信の全貌

近年、オンラインミーティングやライブ配信、マルチプレイヤーゲームなど、リアルタイム性が求められるWebアプリが急増しています。その中で WebRTC はブラウザ間の直接通信を実現する重要技術ですが、複数参加者が同時に映像・音声をやり取りする際に課題が出てきます。そこで登場するのが SFU(Selective Forwarding Unit)、そして 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 改善に活用。

代表的なユースケース

  1. 大規模オンライン会議(Zoom、Microsoft Teams 風)
    1,000 人規模でも Router と Transport を水平分割し、負荷を分散。
  2. ライブストリーミング + 双方向チャット
    プロデューサー側は高解像度映像、視聴者は低解像度の映像と音声だけを受信し、負荷を最小化。
  3. ゲーム内ボイスチャット
    短い音声パケットを低レイテンシで転送し、リアルタイム性が求められるシーンに最適。
  4. 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 は「高度なリアルタイムコミュニケーションを、低コストかつスケーラブルに実装したい」開発者にとって最適な選択肢です。この記事で紹介した概念とサンプルコードを土台に、ぜひ自社サービスや趣味のプロジェクトに組み込んでみてください。