T-CREATOR

WebRTC 最新動向 2025:AV1・SVC・WHIP/WHEP が変えるリアルタイム配信設計

WebRTC 最新動向 2025:AV1・SVC・WHIP/WHEP が変えるリアルタイム配信設計

WebRTC がリアルタイム通信の標準として定着してから数年が経ち、2025 年現在、新しい技術仕様が次々と実用段階に入っています。

特に注目すべきは、次世代コーデック AV1 の本格採用、スケーラビリティを実現する SVC(Scalable Video Coding)、そして配信ワークフローを劇的に簡素化する新プロトコル WHIP/WHEP です。これらの技術は、ビデオ会議から大規模ライブ配信まで、あらゆるリアルタイム配信システムの設計思想を根本から変えようとしています。

本記事では、これら最新技術の仕組みと実装方法、そして実際の配信設計にどう活かすべきかを、初心者の方にもわかりやすく解説します。

背景

WebRTC のこれまでの進化

WebRTC(Web Real-Time Communication)は、2011 年に Google が提案し、2021 年に W3C および IETF で正式に標準化されました。ブラウザ間で直接リアルタイム通信が可能になったことで、ビデオ会議、オンライン授業、ライブ配信など、多様なサービスが生まれました。

しかし、従来の WebRTC には以下のような課題がありました。

従来の WebRTC の主な制約

#項目課題内容
1コーデックVP8/VP9/H.264 が中心で、帯域効率に限界
2スケーラビリティ複数解像度の同時配信が困難
3シグナリング標準化されておらず、実装が複雑
4大規模配信P2P ベースのため、視聴者数に応じてサーバー負荷が増大

これらの課題を解決するために、新しい技術仕様が登場してきました。

新技術が求められる背景

リアルタイム配信のニーズは多様化しています。

たとえば、企業のオンライン会議では 低遅延と安定性 が求められますし、ライブコマースやスポーツ中継では 数千〜数万人への同時配信 が必要です。また、モバイル環境では 帯域の変動に強い配信 が不可欠でしょう。

以下の図は、WebRTC を取り巻く技術進化の全体像を示しています。

mermaidflowchart TB
    legacy["従来の WebRTC<br/>(VP8/VP9/H.264)"] --> needs["多様化するニーズ"]
    needs --> n1["帯域効率の向上"]
    needs --> n2["スケーラブルな配信"]
    needs --> n3["シグナリングの簡素化"]
    n1 --> av1["AV1 コーデック"]
    n2 --> svc["SVC 技術"]
    n3 --> whip["WHIP/WHEP プロトコル"]

    av1 --> future["2025年の<br/>リアルタイム配信"]
    svc --> future
    whip --> future

図で理解できる要点

  • 従来の WebRTC の制約が、新しい技術仕様を生み出す原動力になった
  • AV1・SVC・WHIP/WHEP は、それぞれ異なる課題を解決する
  • これらを組み合わせることで、次世代のリアルタイム配信が実現する

課題

従来のコーデックの限界

VP8 や VP9、H.264 といった従来のコーデックは、リアルタイム通信において十分な性能を発揮してきました。しかし、4K・8K 映像や、モバイル回線での安定配信といった現代の要求には、帯域効率の面で限界があります。

たとえば、1080p の映像を VP9 で配信する場合、2〜3 Mbps 程度の帯域が必要です。これが 4K になると 10 Mbps 以上が必要になり、モバイル環境では不安定になりがちでした。

マルチビットレート配信の複雑さ

従来の WebRTC では、異なるネットワーク環境に対応するために、配信者が複数のビットレートで映像をエンコードする Simulcast という手法が使われてきました。

しかし、Simulcast には以下のような課題がありました。

mermaidflowchart LR
    source["配信者"] -->|高画質| enc1["エンコーダー1<br/>(1080p)"]
    source -->|中画質| enc2["エンコーダー2<br/>(720p)"]
    source -->|低画質| enc3["エンコーダー3<br/>(480p)"]

    enc1 --> sfu["SFU サーバー"]
    enc2 --> sfu
    enc3 --> sfu

    sfu -->|適切な品質を選択| viewer["視聴者"]

この図が示すように、Simulcast では配信者側で 複数のエンコーダーを同時に動かす 必要があり、CPU 負荷が高くなります。また、すべての解像度のストリームをネットワークに送信するため、上り帯域も大量に消費してしまいます。

Simulcast の主な課題

#課題影響
1CPU 負荷が高い配信者のデバイスが発熱・バッテリー消費
2上り帯域を圧迫モバイル環境では配信が不安定に
3実装が複雑開発コストが増大

シグナリングプロトコルの乱立

WebRTC の標準仕様には、シグナリング(接続確立のための情報交換)の方法が定義されていません。そのため、各サービスが独自のシグナリング方式を実装しており、相互運用性が低く、実装コストが高いという問題がありました。

たとえば、ある WebRTC サーバーに接続するには、そのサーバー専用のクライアントライブラリが必要になることが多く、異なるサービス間での映像のやり取りが困難でした。

mermaidsequenceDiagram
    participant Client as クライアント
    participant Server as WebRTC サーバー

    Note over Client,Server: 独自プロトコルでの接続例
    Client->>Server: WebSocket 接続
    Server->>Client: セッション ID 発行
    Client->>Server: カスタム JSON (Offer)
    Server->>Client: カスタム JSON (Answer)
    Client->>Server: カスタム JSON (ICE Candidate)
    Note over Client,Server: プロトコルが標準化されていないため<br/>サーバーごとに実装が異なる

このように、シグナリングの標準化がなされていないことで、開発者は毎回独自の実装を行う必要があり、エコシステムの成長を妨げていました。

解決策

AV1 コーデック:次世代の帯域効率

AV1(AOMedia Video 1)は、Google、Netflix、Amazon などが参加する Alliance for Open Media が開発した、ロイヤリティフリーの次世代動画コーデックです。

AV1 の最大の特徴は、VP9 や H.264 と比較して 30〜50% の帯域削減 を実現できることです。同じ画質であれば、より少ない帯域で配信できるため、モバイル環境や低速回線でも高画質な映像を届けられます。

主要コーデックの比較

#コーデック帯域効率ブラウザ対応ロイヤリティ
1H.264すべて対応有料
2VP9★★主要ブラウザ対応無料
3AV1★★★Chrome/Firefox/Edge 対応無料

2025 年現在、Chrome、Firefox、Edge などの主要ブラウザが AV1 に対応しており、Safari も対応を進めています。

WebRTC での AV1 利用方法

WebRTC で AV1 を利用するには、RTCPeerConnection の設定で AV1 コーデックを優先指定します。

以下は、AV1 を優先的に使用するための設定例です。

typescript// RTCPeerConnection の設定
const configuration: RTCConfiguration = {
  iceServers: [{ urls: 'stun:stun.l.google.com:19302' }],
};

次に、Peer Connection を作成し、コーデックの優先順位を設定します。

typescriptconst peerConnection = new RTCPeerConnection(configuration);

// メディアストリームの取得
const stream = await navigator.mediaDevices.getUserMedia({
  video: { width: 1920, height: 1080 },
});

// トラックを追加
stream.getTracks().forEach((track) => {
  peerConnection.addTrack(track, stream);
});

次に、SDP(Session Description Protocol)を操作して、AV1 を優先するように設定します。

typescript// Offer の作成
const offer = await peerConnection.createOffer();

// SDP を操作して AV1 を優先
const modifiedOffer = {
  type: offer.type,
  sdp: preferCodec(offer.sdp, 'video', 'AV1'),
};

await peerConnection.setLocalDescription(modifiedOffer);

コーデックの優先順位を変更する関数は以下のようになります。

typescript/**
 * SDP 内のコーデック優先順位を変更する関数
 * @param sdp - 元の SDP 文字列
 * @param type - 'audio' または 'video'
 * @param codec - 優先したいコーデック名(例: 'AV1')
 * @returns 変更後の SDP 文字列
 */
function preferCodec(
  sdp: string,
  type: 'audio' | 'video',
  codec: string
): string {
  const lines = sdp.split('\r\n');
  const mLineIndex = lines.findIndex((line) =>
    line.startsWith(`m=${type}`)
  );

  if (mLineIndex === -1) return sdp;

  // コーデックの payload type を検索
  const codecPattern = new RegExp(
    `a=rtpmap:(\\d+) ${codec}`,
    'i'
  );
  const codecLine = lines.find((line) =>
    codecPattern.test(line)
  );

  if (!codecLine) return sdp;

  const match = codecLine.match(codecPattern);
  if (!match) return sdp;

  const payloadType = match[1];

  // m= 行の payload type リストを並び替え
  const mLine = lines[mLineIndex];
  const parts = mLine.split(' ');
  const payloadTypes = parts.slice(3);

  // 指定コーデックを先頭に
  const reordered = [
    payloadType,
    ...payloadTypes.filter((pt) => pt !== payloadType),
  ];

  lines[mLineIndex] = `${parts
    .slice(0, 3)
    .join(' ')} ${reordered.join(' ')}`;

  return lines.join('\r\n');
}

この関数は、SDP の m= 行(メディア記述行)を解析し、指定したコーデックの payload type を先頭に移動させることで、優先順位を変更します。

SVC:1 つのエンコーダーで複数品質を実現

SVC(Scalable Video Coding)は、1 つのエンコードストリームから複数の解像度やフレームレートを取り出せる技術です。

Simulcast が複数のエンコーダーを並列稼働させるのに対し、SVC は 1 つのエンコーダーで階層的にエンコード し、受信側が必要な品質だけを取り出します。

mermaidflowchart TB
    source["配信者"] --> encoder["SVC エンコーダー<br/>(1回のエンコード)"]
    encoder --> layer["階層化されたストリーム"]

    layer --> l1["ベースレイヤー<br/>(480p)"]
    layer --> l2["拡張レイヤー1<br/>(720p)"]
    layer --> l3["拡張レイヤー2<br/>(1080p)"]

    l1 --> sfu["SFU サーバー"]
    l2 --> sfu
    l3 --> sfu

    sfu -->|低速回線| v1["視聴者A<br/>(480p受信)"]
    sfu -->|中速回線| v2["視聴者B<br/>(720p受信)"]
    sfu -->|高速回線| v3["視聴者C<br/>(1080p受信)"]

図で理解できる要点

  • SVC では 1 回のエンコードで複数の品質レイヤーを生成
  • SFU サーバーが視聴者の回線状況に応じて、適切なレイヤーを配信
  • 配信者側の CPU 負荷と上り帯域が大幅に削減される

SVC と Simulcast の比較

#項目SimulcastSVC
1エンコード回数複数回(品質ごと)1 回
2CPU 負荷高い低い
3上り帯域大量消費効率的
4ブラウザ対応広いChrome/Edge(拡大中)

WebRTC での SVC 実装

WebRTC で SVC を利用するには、RTCRtpSendersetParameters メソッドでエンコーディングパラメータを設定します。

以下は、AV1 SVC を利用した実装例です。

typescript// Peer Connection の作成
const peerConnection = new RTCPeerConnection(configuration);

// ビデオトラックの取得
const stream = await navigator.mediaDevices.getUserMedia({
  video: { width: 1920, height: 1080 },
});

const videoTrack = stream.getVideoTracks()[0];
const sender = peerConnection.addTrack(videoTrack, stream);

次に、SVC のエンコーディングパラメータを設定します。

typescript// SVC のエンコーディング設定
const parameters = sender.getParameters();

// AV1 の場合、scalabilityMode で SVC レイヤーを指定
parameters.encodings = [
  {
    scalabilityMode: 'L3T3', // 3つの空間レイヤー、3つの時間レイヤー
    maxBitrate: 3000000, // 3 Mbps
  },
];

await sender.setParameters(parameters);

scalabilityMode の指定方法は以下のとおりです。

typescript/**
 * scalabilityMode の形式
 * L{空間レイヤー数}T{時間レイヤー数}
 *
 * 例:
 * - L1T3: 1つの解像度、3つのフレームレート
 * - L3T3: 3つの解像度、3つのフレームレート
 */

// 一般的な設定例
const scalabilityModes = {
  // フルHD 配信(3つの解像度レイヤー)
  highQuality: 'L3T3',

  // HD 配信(2つの解像度レイヤー)
  mediumQuality: 'L2T3',

  // SD 配信(1つの解像度のみ)
  lowQuality: 'L1T2',
};

SFU サーバー側では、視聴者の帯域状況に応じて適切なレイヤーを選択して配信します。

typescript// SFU サーバー側での実装例(概念コード)
class SVCRouter {
  /**
   * 視聴者の帯域に応じて適切なレイヤーを選択
   * @param availableBandwidth - 利用可能な帯域(bps)
   * @returns 配信すべきレイヤー情報
   */
  selectLayer(availableBandwidth: number) {
    if (availableBandwidth > 2000000) {
      // 2Mbps 以上: フル品質
      return { spatialLayer: 2, temporalLayer: 2 };
    } else if (availableBandwidth > 1000000) {
      // 1Mbps 以上: 中品質
      return { spatialLayer: 1, temporalLayer: 2 };
    } else {
      // 1Mbps 未満: 低品質
      return { spatialLayer: 0, temporalLayer: 1 };
    }
  }
}

WHIP/WHEP:標準化されたシグナリング

WHIP(WebRTC-HTTP Ingestion Protocol)と WHEP(WebRTC-HTTP Egress Protocol)は、WebRTC のシグナリングを HTTP ベースで標準化した新しいプロトコルです。

  • WHIP: 配信者からサーバーへの映像送信プロトコル
  • WHEP: サーバーから視聴者への映像配信プロトコル

従来は WebSocket や独自 API でシグナリングを実装していましたが、WHIP/WHEP を使えば HTTP POST リクエスト 1 回 で接続が確立できます。

mermaidsequenceDiagram
    participant Pub as 配信者
    participant Server as WHIP サーバー
    participant Sub as 視聴者

    Note over Pub,Server: WHIP による配信開始
    Pub->>Pub: SDP Offer 生成
    Pub->>Server: POST /whip<br/>(SDP Offer)
    Server->>Pub: 201 Created<br/>(SDP Answer)
    Note over Pub,Server: 1回の HTTP リクエストで接続完了

    Note over Server,Sub: WHEP による視聴開始
    Sub->>Sub: SDP Offer 生成
    Sub->>Server: POST /whep<br/>(SDP Offer)
    Server->>Sub: 201 Created<br/>(SDP Answer)
    Note over Server,Sub: 1回の HTTP リクエストで接続完了

WHIP/WHEP の利点

#利点詳細
1シンプルHTTP POST のみで接続確立
2標準化IETF で標準化済み(RFC 9414, 9418)
3相互運用性異なるサーバー間でも動作
4実装容易既存の HTTP インフラを活用

WHIP での配信実装

WHIP を使った配信者側の実装例を見ていきましょう。

まず、メディアストリームを取得し、Offer を生成します。

typescript// メディアストリームの取得
const stream = await navigator.mediaDevices.getUserMedia({
  video: { width: 1920, height: 1080 },
  audio: true,
});

// Peer Connection の作成
const peerConnection = new RTCPeerConnection({
  iceServers: [{ urls: 'stun:stun.l.google.com:19302' }],
});

// トラックの追加
stream.getTracks().forEach((track) => {
  peerConnection.addTrack(track, stream);
});

次に、SDP Offer を生成し、WHIP サーバーに送信します。

typescript// SDP Offer の生成
const offer = await peerConnection.createOffer();
await peerConnection.setLocalDescription(offer);

// WHIP サーバーへの送信
const whipUrl = 'https://example.com/whip/stream-id';

const response = await fetch(whipUrl, {
  method: 'POST',
  headers: {
    'Content-Type': 'application/sdp',
    Authorization: 'Bearer YOUR_TOKEN', // 認証が必要な場合
  },
  body: offer.sdp,
});

サーバーからの応答(SDP Answer)を受け取り、接続を確立します。

typescriptif (response.status === 201) {
  // SDP Answer の取得
  const answerSdp = await response.text();

  // リソース URL の取得(切断時に使用)
  const resourceUrl = response.headers.get('Location');

  // Answer の設定
  await peerConnection.setRemoteDescription({
    type: 'answer',
    sdp: answerSdp,
  });

  console.log('WHIP 接続確立完了');

  // 切断時のクリーンアップ用に URL を保存
  return resourceUrl;
} else {
  throw new Error(`WHIP 接続失敗: ${response.status}`);
}

配信を停止する際は、取得した Resource URL に DELETE リクエストを送信します。

typescript/**
 * WHIP 配信の停止
 * @param resourceUrl - 接続時に取得した Resource URL
 */
async function stopWhipStream(resourceUrl: string) {
  await fetch(resourceUrl, {
    method: 'DELETE',
    headers: {
      Authorization: 'Bearer YOUR_TOKEN',
    },
  });

  console.log('WHIP 配信停止');
}

WHEP での視聴実装

視聴者側では WHEP を使って映像を受信します。

typescript// Peer Connection の作成(受信専用)
const peerConnection = new RTCPeerConnection({
  iceServers: [{ urls: 'stun:stun.l.google.com:19302' }],
});

// トランシーバーの追加(受信専用)
peerConnection.addTransceiver('video', {
  direction: 'recvonly',
});
peerConnection.addTransceiver('audio', {
  direction: 'recvonly',
});

次に、SDP Offer を生成し、WHEP サーバーに送信します。

typescript// SDP Offer の生成
const offer = await peerConnection.createOffer();
await peerConnection.setLocalDescription(offer);

// WHEP サーバーへの送信
const whepUrl = 'https://example.com/whep/stream-id';

const response = await fetch(whepUrl, {
  method: 'POST',
  headers: {
    'Content-Type': 'application/sdp',
  },
  body: offer.sdp,
});

サーバーからの応答を受け取り、受信ストリームを video 要素にバインドします。

typescriptif (response.status === 201) {
  const answerSdp = await response.text();
  const resourceUrl = response.headers.get('Location');

  await peerConnection.setRemoteDescription({
    type: 'answer',
    sdp: answerSdp,
  });

  // 受信ストリームの処理
  peerConnection.ontrack = (event) => {
    const videoElement = document.querySelector('video');
    if (videoElement) {
      videoElement.srcObject = event.streams[0];
    }
  };

  return resourceUrl;
}

WHEP でも、視聴を停止する際は Resource URL に DELETE リクエストを送信します。

typescriptasync function stopWhepStream(resourceUrl: string) {
  await fetch(resourceUrl, { method: 'DELETE' });
  console.log('WHEP 視聴停止');
}

具体例

AV1 + SVC + WHIP/WHEP を組み合わせた配信システム

ここでは、これまで解説した技術をすべて組み合わせた、最新の WebRTC 配信システムを構築してみます。

システム全体の構成は以下のとおりです。

mermaidflowchart TB
    pub["配信者<br/>(ブラウザ)"] -->|WHIP<br/>AV1 SVC| sfu["SFU サーバー<br/>(mediasoup/Janus)"]

    sfu -->|WHEP<br/>高品質レイヤー| sub1["視聴者A<br/>(高速回線)"]
    sfu -->|WHEP<br/>中品質レイヤー| sub2["視聴者B<br/>(中速回線)"]
    sfu -->|WHEP<br/>低品質レイヤー| sub3["視聴者C<br/>(低速回線)"]

    sfu -.->|統計情報| monitor["モニタリング<br/>システム"]

図で理解できる要点

  • 配信者は WHIP で AV1 SVC エンコードされた映像を送信
  • SFU サーバーが視聴者ごとに最適なレイヤーを選択
  • 視聴者は WHEP でシンプルに受信
  • モニタリングシステムで配信品質を監視

配信者側の実装(統合版)

すべての技術を統合した配信クライアントを実装します。

まず、設定情報とインターフェースを定義します。

typescript/**
 * WHIP 配信の設定
 */
interface WhipPublisherConfig {
  whipUrl: string; // WHIP エンドポイント URL
  authToken?: string; // 認証トークン(オプション)
  videoConstraints: MediaTrackConstraints; // 映像制約
  scalabilityMode: string; // SVC モード(例: 'L3T3')
}

/**
 * 配信状態
 */
interface PublishState {
  isPublishing: boolean;
  resourceUrl?: string;
  peerConnection?: RTCPeerConnection;
  stream?: MediaStream;
}

次に、配信クラスを実装します。

typescriptclass WhipAV1Publisher {
  private state: PublishState = {
    isPublishing: false
  };

  constructor(private config: WhipPublisherConfig) {}

  /**
   * 配信開始
   */
  async startPublish(): Promise<void> {
    // メディアストリームの取得
    this.state.stream = await navigator.mediaDevices.getUserMedia({
      video: this.config.videoConstraints,
      audio: {
        echoCancellation: true,
        noiseSuppression: true,
        sampleRate: 48000
      }
    });

    // Peer Connection の作成
    this.state.peerConnection = new RTCPeerConnection({
      iceServers: [
        { urls: 'stun:stun.l.google.com:19302' }
      ]
    });

    // トラックの追加とSVC設定
    await this.addTracksWithSVC();

    // WHIP接続の確立
    await this.establishWhipConnection();

    this.state.isPublishing = true;
  }

  /**
   * トラック追加と SVC 設定
   */
  private async addTracksWithSVC(): Promise<void> {
    const pc = this.state.peerConnection!;
    const stream = this.state.stream!;

    // ビデオトラックの追加
    const videoTrack = stream.getVideoTracks()[0];
    const videoSender = pc.addTrack(videoTrack, stream);

    // AV1 SVC の設定
    const params = videoSender.getParameters();
    params.encodings = [
      {
        scalabilityMode: this.config.scalabilityMode,
        maxBitrate: 3000000 // 3 Mbps
      }
    ];
    await videoSender.setParameters(params);

    // オーディオトラックの追加
    const audioTrack = stream.getAudioTracks()[0];
    pc.addTrack(audioTrack, stream);
  }

WHIP による接続確立処理を実装します。

typescript  /**
   * WHIP 接続の確立
   */
  private async establishWhipConnection(): Promise<void> {
    const pc = this.state.peerConnection!;

    // Offer の生成
    const offer = await pc.createOffer();

    // AV1 を優先
    const modifiedOffer = {
      type: offer.type as RTCSdpType,
      sdp: this.preferCodec(offer.sdp!, 'video', 'AV1')
    };

    await pc.setLocalDescription(modifiedOffer);

    // WHIP サーバーへ送信
    const response = await fetch(this.config.whipUrl, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/sdp',
        ...(this.config.authToken && {
          'Authorization': `Bearer ${this.config.authToken}`
        })
      },
      body: modifiedOffer.sdp
    });

    if (response.status !== 201) {
      throw new Error(`WHIP 接続失敗: ${response.status}`);
    }

    // Answer の取得と設定
    const answerSdp = await response.text();
    this.state.resourceUrl = response.headers.get('Location')!;

    await pc.setRemoteDescription({
      type: 'answer',
      sdp: answerSdp
    });
  }

コーデック優先順位の変更と配信停止処理を実装します。

typescript  /**
   * コーデック優先順位の変更
   */
  private preferCodec(sdp: string, type: string, codec: string): string {
    const lines = sdp.split('\r\n');
    const mLineIndex = lines.findIndex(line => line.startsWith(`m=${type}`));

    if (mLineIndex === -1) return sdp;

    const codecPattern = new RegExp(`a=rtpmap:(\\d+) ${codec}`, 'i');
    const codecLine = lines.find(line => codecPattern.test(line));

    if (!codecLine) return sdp;

    const match = codecLine.match(codecPattern);
    if (!match) return sdp;

    const payloadType = match[1];
    const mLine = lines[mLineIndex];
    const parts = mLine.split(' ');
    const payloadTypes = parts.slice(3);

    const reordered = [
      payloadType,
      ...payloadTypes.filter(pt => pt !== payloadType)
    ];

    lines[mLineIndex] = `${parts.slice(0, 3).join(' ')} ${reordered.join(' ')}`;
    return lines.join('\r\n');
  }

  /**
   * 配信停止
   */
  async stopPublish(): Promise<void> {
    if (!this.state.isPublishing || !this.state.resourceUrl) {
      return;
    }

    // WHIP リソースの削除
    await fetch(this.state.resourceUrl, {
      method: 'DELETE',
      headers: this.config.authToken ? {
        'Authorization': `Bearer ${this.config.authToken}`
      } : {}
    });

    // Peer Connection のクローズ
    this.state.peerConnection?.close();

    // ストリームの停止
    this.state.stream?.getTracks().forEach(track => track.stop());

    this.state.isPublishing = false;
  }
}

視聴者側の実装(統合版)

視聴者側も同様に、WHEP を使った実装を行います。

typescript/**
 * WHEP 視聴の設定
 */
interface WhepSubscriberConfig {
  whepUrl: string;           // WHEP エンドポイント URL
  videoElement: HTMLVideoElement; // 再生用 video 要素
}

class WhepAV1Subscriber {
  private resourceUrl?: string;
  private peerConnection?: RTCPeerConnection;

  constructor(private config: WhepSubscriberConfig) {}

  /**
   * 視聴開始
   */
  async startSubscribe(): Promise<void> {
    // Peer Connection の作成
    this.peerConnection = new RTCPeerConnection({
      iceServers: [
        { urls: 'stun:stun.l.google.com:19302' }
      ]
    });

    // トランシーバーの追加(受信専用)
    this.peerConnection.addTransceiver('video', {
      direction: 'recvonly'
    });
    this.peerConnection.addTransceiver('audio', {
      direction: 'recvonly'
    });

    // 受信ストリームの処理
    this.peerConnection.ontrack = (event) => {
      this.config.videoElement.srcObject = event.streams[0];
    };

    // WHEP 接続の確立
    await this.establishWhepConnection();
  }

WHEP による接続確立と停止処理を実装します。

typescript  /**
   * WHEP 接続の確立
   */
  private async establishWhepConnection(): Promise<void> {
    const pc = this.peerConnection!;

    // Offer の生成
    const offer = await pc.createOffer();
    await pc.setLocalDescription(offer);

    // WHEP サーバーへ送信
    const response = await fetch(this.config.whepUrl, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/sdp'
      },
      body: offer.sdp
    });

    if (response.status !== 201) {
      throw new Error(`WHEP 接続失敗: ${response.status}`);
    }

    // Answer の取得と設定
    const answerSdp = await response.text();
    this.resourceUrl = response.headers.get('Location')!;

    await pc.setRemoteDescription({
      type: 'answer',
      sdp: answerSdp
    });
  }

  /**
   * 視聴停止
   */
  async stopSubscribe(): Promise<void> {
    if (!this.resourceUrl) return;

    // WHEP リソースの削除
    await fetch(this.resourceUrl, { method: 'DELETE' });

    // Peer Connection のクローズ
    this.peerConnection?.close();

    // video 要素のクリア
    this.config.videoElement.srcObject = null;
  }
}

実際の利用例

実装したクラスを使って、配信と視聴を開始する例です。

typescript// 配信者側
const publisher = new WhipAV1Publisher({
  whipUrl: 'https://sfu.example.com/whip/my-stream',
  authToken: 'your-auth-token',
  videoConstraints: {
    width: { ideal: 1920 },
    height: { ideal: 1080 },
    frameRate: { ideal: 30 },
  },
  scalabilityMode: 'L3T3', // 3つの空間レイヤー、3つの時間レイヤー
});

// 配信開始
await publisher.startPublish();

// 配信停止(必要に応じて)
// await publisher.stopPublish();

視聴者側の実装例です。

typescript// 視聴者側
const videoElement = document.querySelector('video')!;

const subscriber = new WhepAV1Subscriber({
  whepUrl: 'https://sfu.example.com/whep/my-stream',
  videoElement: videoElement,
});

// 視聴開始
await subscriber.startSubscribe();

// 視聴停止(必要に応じて)
// await subscriber.stopSubscribe();

期待される効果

この統合システムを導入することで、以下のような効果が期待できます。

システム改善効果

#項目従来方式新方式(AV1+SVC+WHIP/WHEP)改善率
1帯域消費量3.0 Mbps (VP9)2.0 Mbps (AV1)▲33%
2配信者 CPU 負荷80% (Simulcast)40% (SVC)▲50%
3実装コード量500 行 (独自)200 行 (WHIP/WHEP)▲60%
4接続確立時間2-3 秒 (WebSocket)0.5-1 秒 (HTTP)▲70%

特にモバイル環境では、帯域削減と CPU 負荷軽減により、バッテリー持続時間が大幅に改善されます。また、WHIP/WHEP によるシンプルな実装は、開発期間の短縮とメンテナンスコストの削減につながります。

まとめ

WebRTC の最新技術である AV1 コーデックSVCWHIP/WHEP プロトコル は、それぞれが異なる課題を解決し、組み合わせることで強力なリアルタイム配信システムを構築できることがわかりました。

各技術の役割と効果

  • AV1: 従来比 30〜50% の帯域削減で、同じ画質をより少ない帯域で配信
  • SVC: 1 回のエンコードで複数品質を実現し、配信者の CPU 負荷と帯域を削減
  • WHIP/WHEP: HTTP ベースの標準化により、シグナリング実装を大幅に簡素化

これらの技術は、2025 年現在、主要ブラウザや SFU サーバー(mediasoup、Janus など)で実用段階に入っています。特に、以下のようなユースケースで大きな効果を発揮するでしょう。

推奨ユースケース

  • 大規模ライブ配信: 数千〜数万人への同時配信でも帯域コストを抑制
  • モバイル配信: バッテリー消費を抑えながら高画質配信を実現
  • グローバル配信: 多様なネットワーク環境に対応した品質最適化
  • 低遅延配信: 従来の HLS/DASH より圧倒的に低い遅延(1 秒以下)

実装にあたっては、まず WHIP/WHEP で基本的な配信フローを構築し、次に SVC で品質の最適化を行い、最後に AV1 で帯域効率を高めるという段階的なアプローチが効果的です。

WebRTC の進化は今も続いており、今後も新しい技術仕様が登場するでしょう。しかし、本記事で紹介した技術は、今後数年間のリアルタイム配信設計の基盤となる重要な要素です。ぜひ、実際のプロジェクトに取り入れてみてください。

関連リンク