EME と WTF

Encrypted Media Extensions の概要

Encrypted Media Extensions は、ウェブ アプリケーションとコンテンツ保護システムとのやり取りを可能にする API を提供し、暗号化された音声と動画を再生できるようにします。

EME は、基盤となる保護システムに関係なく、どのブラウザでも同じアプリと暗号化されたファイルを使用できるようにするように設計されています。前者は標準化された API とフローによって可能になり、後者は共通暗号化のコンセプトによって可能になりました。

EME は HTMLMediaElement 仕様の拡張版であり、名前の由来になっています。「拡張機能」であるということは、ブラウザでの EME のサポートが任意であることを意味します。暗号化されたメディアをサポートしていないブラウザでは、暗号化されたメディアを再生できませんが、HTML 仕様に準拠するうえで EME は必要ありません。EME 仕様より:

EME の実装では、次の外部コンポーネントを使用します。

  • 鍵システム: コンテンツ保護(DRM)メカニズム。EME では、クリアキーを除き、鍵システム自体は定義されません(詳細は下記を参照)。
  • コンテンツ復号モジュール(CDM): 暗号化されたメディアの再生を可能にする、クライアント側のソフトウェアまたはハードウェアのメカニズム。主要システムと同様に、EME でも CDM は定義されていませんが、アプリケーションが利用可能な CDM とやり取りするためのインターフェースを提供します。
  • ライセンス(鍵)サーバー: CDM と連携して、メディアを復号するための鍵を提供します。ライセンス サーバーとのネゴシエーションは、アプリケーションが担います。
  • パッケージング サービス: 配信や消費のためにメディアをエンコードおよび暗号化する。

EME を使用するアプリケーションは、ライセンス サーバーとやり取りして復号に必要な鍵を取得しますが、ユーザー ID と認証は EME には含まれません。メディア再生を有効にするためのキーの取得は、(必要に応じて)ユーザーを認証した後で行われます。Netflix などのサービスは、ウェブ アプリケーション内でユーザーを認証する必要があります。ユーザーがアプリケーションにログインすると、アプリケーションがユーザーの ID と権限を判別します。

EME の仕組み

EME のコンポーネントがどのように相互作用するかを以下に示します。それぞれのコード例を以下に示します。

  1. ウェブ アプリケーションが、暗号化されたストリームを 1 つ以上含む音声または動画を再生しようとしています。
  2. ブラウザは、メディアが暗号化されていることを認識し(詳細は下のボックスを参照)、暗号化に関するメディアから取得したメタデータ(initData)とともに encrypted イベントを呼び出します。
  3. アプリは encrypted イベントを処理します。
    1. メディア要素に MediaKeys オブジェクトが関連付けられていない場合は、まず navigator.requestMediaKeySystemAccess() を使用して使用可能なキーシステムを選択し、使用可能なキーシステムを確認してから、MediaKeySystemAccess オブジェクトを使用して、使用可能なキーシステムの MediaKeys オブジェクトを作成します。MediaKeys オブジェクトの初期化は、最初の encrypted イベントの前に行う必要があります。ライセンス サーバーの URL の取得は、利用可能なキーシステムの選択とは関係なく、アプリによって行われます。MediaKeys オブジェクトは、音声要素または動画要素のメディアの復号に使用できるすべてのキーを表します。CDM インスタンスを表し、CDM へのアクセスを提供します。具体的には、ライセンス サーバーから鍵を取得するために使用されるキー セッションを作成します。
    2. 作成した MediaKeys オブジェクトをメディア要素に割り当てます。setMediaKeys()MediaKeys オブジェクトを HTMLMediaElement と関連付け、再生時(デコード時)にそのキーを使用できるようにします。
  4. アプリは MediaKeyscreateSession() を呼び出して MediaKeySession を作成します。これにより、ライセンスとそのキーの存続期間を表す MediaKeySession が作成されます。
  5. アプリは、MediaKeySessiongenerateRequest() を呼び出して、encrypted ハンドラで取得したメディアデータを CDM に渡して、ライセンス リクエストを生成します。
  6. CDM が message イベント(ライセンス サーバーからキーを取得するリクエスト)を発行します。
  7. MediaKeySession オブジェクトが message イベントを受信し、アプリが(XHR などを介して)ライセンス サーバーにメッセージを送信します。
  8. アプリは、ライセンス サーバーから応答を受け取り、MediaKeySessionupdate() メソッドを使用してデータを CDM に渡します。
  9. CDM は、ライセンス内の鍵を使用してメディアを復号します。メディア要素に関連付けられた MediaKey 内の任意のセッションから、有効なキーを使用できます。CDM は、鍵 ID でインデックスに登録された鍵とポリシーにアクセスします。
  10. メディアの再生が再開されます。

ふー...

CDM とライセンス サーバーの間には複数のメッセージが存在する可能性があり、このプロセスにおけるすべての通信はブラウザとアプリからは不透明です。メッセージは CDM とライセンス サーバーによってのみ認識されますが、CDM が送信するメッセージのタイプはアプリレイヤで確認できます。ライセンス リクエストには、CDM の有効性(および信頼関係)の証明と、生成されるライセンスのコンテンツ キーを暗号化する際に使用するキーが含まれます。

では、CDM の実際の役割はどのようなものでしょうか。

EME の実装自体は、メディアを復号する方法を提供しません。ウェブ アプリケーションがコンテンツ復号モジュールとやり取りするための API を提供するだけです。

CDM が実際に何を行うかは EME 仕様では定義されておらず、CDM はメディアのデコード(解凍)と復号を処理する場合があります。CDM 機能には、以下のような選択肢があります。堅牢性が劣るものから順に、以下の選択肢があります。

  • 復号のみ。通常のメディア パイプライン(<video> 要素など)を使用して再生を有効にします。
  • 復号とデコード。レンダリングのために動画フレームをブラウザに渡します。
  • 復号とデコード、ハードウェア(GPU など)で直接レンダリング。

ウェブアプリで CDM を利用できるようにするには、いくつかの方法があります。

  • CDM をブラウザにバンドルする。
  • CDM を個別に配布します。
  • CDM をオペレーティング システムに組み込みます。
  • ファームウェアに CDM を組み込みます。
  • CDM をハードウェアに埋め込みます。

CDM の提供方法は EME の仕様では定義されていませんが、いずれの場合もブラウザが CDM の調査と公開を行います。

EME では特定の鍵システムを必須としていません。現在のデスクトップ ブラウザとモバイル ブラウザでは、Chrome は Widevine をサポートし、IE11 は PlayReady をサポートしています。

ライセンス サーバーからキーを取得する

一般的な商用利用では、コンテンツはパッケージングサービスやツールを使用して暗号化およびエンコードされます。暗号化されたメディアがオンラインで利用できるようになると、ウェブ クライアントはライセンス サーバーから鍵(ライセンスに含まれる)を取得し、その鍵を使用してコンテンツを復号して再生できるようになります。

次のコード(仕様の例から抜粋したコード)は、アプリが適切なキーシステムを選択し、ライセンス サーバーからキーを取得する方法を示しています。

var video = document.querySelector('video');

var config = [{initDataTypes: ['webm'],
  videoCapabilities: [{contentType: 'video/webm; codecs="vp9"'}]}];

if (!video.mediaKeys) {
  navigator.requestMediaKeySystemAccess('org.w3.clearkey',
      config).then(
    function(keySystemAccess) {
      var promise = keySystemAccess.createMediaKeys();
      promise.catch(
        console.error.bind(console, 'Unable to create MediaKeys')
      );
      promise.then(
        function(createdMediaKeys) {
          return video.setMediaKeys(createdMediaKeys);
        }
      ).catch(
        console.error.bind(console, 'Unable to set MediaKeys')
      );
      promise.then(
        function(createdMediaKeys) {
          var initData = new Uint8Array([...]);
          var keySession = createdMediaKeys.createSession();
          keySession.addEventListener('message', handleMessage,
              false);
          return keySession.generateRequest('webm', initData);
        }
      ).catch(
        console.error.bind(console,
          'Unable to create or initialize key session')
      );
    }
  );
}

function handleMessage(event) {
  var keySession = event.target;
  var license = new Uint8Array([...]);
  keySession.update(license).catch(
    console.error.bind(console, 'update() failed')
  );
}

共通暗号化

共通暗号化ソリューションを使用すると、コンテンツ プロバイダは、コンテナ/コーデックごとにコンテンツを暗号化してパッケージ化し、さまざまな鍵システム、CDM、クライアント(共通暗号化をサポートするすべての CDM)で使用できます。たとえば、Playready を使用してパッケージ化された動画は、Widevine CDM を使用して Widevine ライセンス サーバーからキーを取得するブラウザ内で再生できます。

従来のソリューションでは、アプリケーション ランタイムも含まれることが多い単一のクライアントなど、完全な垂直スタックでのみ機能する従来のソリューションとは対照的です。

Common Encryption(CENC)は、ISO BMFF の保護方式を定義する ISO 標準です。同様の概念が WebM にも適用されます。

キーを消去

EME では DRM 機能が定義されていませんが、現在の仕様では EME をサポートするすべてのブラウザにクリアキーを実装する必要があります。このシステムを使用すると、鍵でメディアを暗号化し、その鍵を提供するだけで再生できます。クリアキーはブラウザに組み込むことができます。個別の復号モジュールを使用する必要はありません。

Clear Key は、多くの商用コンテンツで使用される可能性は低いものの、EME をサポートするすべてのブラウザで完全に相互運用できます。また、ライセンス サーバーにコンテンツ キーをリクエストしなくても、EME の実装や EME を使用するアプリケーションのテストにも便利です。simpl.info/ck に、クリアキーの簡単な例があります。以下にコードのチュートリアルを示します。前述のステップとほぼ同じですが、ライセンス サーバーの操作は必要ありません。

// Define a key: hardcoded in this example
// – this corresponds to the key used for encryption
var KEY = new Uint8Array([
  0xeb, 0xdd, 0x62, 0xf1, 0x68, 0x14, 0xd2, 0x7b,
  0x68, 0xef, 0x12, 0x2a, 0xfc, 0xe4, 0xae, 0x3c
]);

var config = [{
  initDataTypes: ['webm'],
  videoCapabilities: [{
    contentType: 'video/webm; codecs="vp8"'
  }]
}];

var video = document.querySelector('video');
video.addEventListener('encrypted', handleEncrypted, false);

navigator.requestMediaKeySystemAccess('org.w3.clearkey', config).then(
  function(keySystemAccess) {
    return keySystemAccess.createMediaKeys();
  }
).then(
  function(createdMediaKeys) {
    return video.setMediaKeys(createdMediaKeys);
  }
).catch(
  function(error) {
    console.error('Failed to set up MediaKeys', error);
  }
);

function handleEncrypted(event) {
  var session = video.mediaKeys.createSession();
  session.addEventListener('message', handleMessage, false);
  session.generateRequest(event.initDataType, event.initData).catch(
    function(error) {
      console.error('Failed to generate a license request', error);
    }
  );
}

function handleMessage(event) {
  // If you had a license server, you would make an asynchronous XMLHttpRequest
  // with event.message as the body.  The response from the server, as a
  // Uint8Array, would then be passed to session.update().
  // Instead, we will generate the license synchronously on the client, using
  // the hard-coded KEY at the top.
  var license = generateLicense(event.message);

  var session = event.target;
  session.update(license).catch(
    function(error) {
      console.error('Failed to update the session', error);
    }
  );
}

// Convert Uint8Array into base64 using base64url alphabet, without padding.
function toBase64(u8arr) {
  return btoa(String.fromCharCode.apply(null, u8arr)).
      replace(/\+/g, '-').replace(/\//g, '_').replace(/=*$/, '');
}

// This takes the place of a license server.
// kids is an array of base64-encoded key IDs
// keys is an array of base64-encoded keys
function generateLicense(message) {
  // Parse the clearkey license request.
  var request = JSON.parse(new TextDecoder().decode(message));
  // We only know one key, so there should only be one key ID.
  // A real license server could easily serve multiple keys.
  console.assert(request.kids.length === 1);

  var keyObj = {
    kty: 'oct',
    alg: 'A128KW',
    kid: request.kids[0],
    k: toBase64(KEY)
  };
  return new TextEncoder().encode(JSON.stringify({
    keys: [keyObj]
  }));
}

このコードをテストするには、再生する動画を暗号化する必要があります。クリアキーで使用する動画の暗号化は、webm_crypt の手順に沿って WebM に対して行うことができます。商用サービス(少なくとも ISO BMFF/MP4)も利用可能で、他のソリューションも開発中です。

Media Source Extensions(MSE)

HTMLMediaElement は、シンプルな美しさを特徴とする生き物です。

src URL を指定するだけで、メディアの読み込み、デコード、再生ができます。

<video src='foo.webm'></video>

Media Source API は HTMLMediaElement の拡張機能であり、JavaScript で動画の「チャンク」から再生するストリームを作成できるようにすることで、メディアのソースをよりきめ細かく制御できます。これにより、アダプティブ ストリーミングやタイムシフトなどの手法が可能になります。

EME にとって MSE が重要な理由商用コンテンツ プロバイダは、保護されたコンテンツを配信するだけでなく、ネットワークの状況やその他の要件に合わせてコンテンツ配信を調整できる必要があるからです。たとえば、Netflix はネットワーク状態の変化に応じてストリーミングのビットレートを動的に変更します。EME は、src 属性を介して提供されるメディアの場合と同様に、MSE 実装によって提供されるメディア ストリームの再生でも動作します。

異なるビットレートでエンコードされたメディアをチャンクして再生する方法以下の DASH セクションをご覧ください。

MSE の動作は simpl.info/mse で確認できます。この例では、File API を使用して WebM 動画が 5 つのチャンクに分割されます。本番環境のアプリケーションでは、動画のチャンクは Ajax で取得します。

まず、SourceBuffer が作成されます。

var sourceBuffer = mediaSource.addSourceBuffer('video/webm; codecs="vorbis,vp8"');

次に、appendBuffer() メソッドを使用して各チャンクを追加することで、映画全体が動画要素に「ストリーミング」されます。

reader.onload = function (e) {
  sourceBuffer.appendBuffer(new Uint8Array(e.target.result));
  if (i === NUM_CHUNKS - 1) {
    mediaSource.endOfStream();
  } else {
    if (video.paused) {
      // start playing after first chunk is appended
      video.play();
    }
    readChunk_(++i);
  }
};

MSE について詳しくは、HTML5 Rocks の記事をご覧ください。

Dynamic Adaptive Streaming over HTTP(DASH)

マルチデバイス、マルチプラットフォーム、モバイルなど、いずれにせよ、ウェブは接続性が変化する状況下でしばしば経験されます。マルチデバイスの世界で帯域幅の制約や変動性に対処するには、動的でアダプティブな配信が不可欠です。

DASH(別名 MPEG-DASH)は、不安定な環境において、ストリーミングとダウンロードの両方で最適なメディア配信を実現するように設計されています。Apple の HTTP Live Streaming(HLS)や Microsoft の Smooth Streaming など、他のテクノロジーも同様の機能を備えていますが、DASH は、オープン スタンダードに基づく HTTP 経由のアダプティブ ビットレート ストリーミングで唯一の方法です。DASH はすでに YouTube などのサイトで使用されています。

EME や MSE との関係MSE ベースの DASH 実装では、既存の HTTP インフラストラクチャを使用して、マニフェストを解析し、適切なビットレートで動画のセグメントをダウンロードし、空腹時に動画要素にフィードできます。

つまり、商用コンテンツ プロバイダは DASH を使用することで、保護されたコンテンツのアダプティブ ストリーミングを実現できます。

DASH は以下のような処理を行います。

  • 動的: 状況の変化に対応します。
  • アダプティブ: 音声または動画の適切なビットレートを使用するように適応します。
  • ストリーミング: ストリーミングとダウンロードが可能です。
  • HTTP: 従来のストリーミング サーバーの欠点がなく、HTTP の利点を活かしてコンテンツ配信を行うことができます。

BBC は DASH を使用したテスト ストリームの提供を開始しました。

まとめ

  1. メディアが異なるビットレートでエンコードされている。
  2. 各ビットレート ファイルは HTTP サーバーから利用可能になります。
  3. クライアント ウェブアプリは、DASH を使用して取得して再生するビットレートを選択します。

動画セグメンテーション プロセスの一環として、Media Presentation Description(MPD)と呼ばれる XML マニフェストがプログラムで作成されます。ここでは、アダプテーション セットと表現について説明します。再生時間と URL も指定します。MPD は次のようになります。

<MPD xmlns="urn:mpeg:DASH:schema:MPD:2011" mediaPresentationDuration="PT0H3M1.63S" minBufferTime="PT1.5S" profiles="urn:mpeg:dash:profile:isoff-on-demand:2011"
type="static">
  <Period duration="PT0H3M1.63S" start="PT0S">
    <AdaptationSet>
      <ContentComponent contentType="video" id="1" />
      <Representation bandwidth="4190760" codecs="avc1.640028" height="1080" id="1" mimeType="video/mp4" width="1920">
        <BaseURL>car-20120827-89.mp4</BaseURL>
        <SegmentBase indexRange="674-1149">
          <Initialization range="0-673" />
        </SegmentBase>
      </Representation>
      <Representation bandwidth="2073921" codecs="avc1.4d401f" height="720" id="2" mimeType="video/mp4" width="1280">
        <BaseURL>car-20120827-88.mp4</BaseURL>
        <SegmentBase indexRange="708-1183">
          <Initialization range="0-707" />
        </SegmentBase>
      </Representation>

      …

    </AdaptationSet>
  </Period>
</MPD>

(この XML は、YouTube DASH デモ プレーヤーに使用する .mpd ファイルから取得されています)。

DASH の仕様によれば、理論的には MPD ファイルを動画の src として使用できます。ただし、ウェブ デベロッパーに柔軟性を提供するために、ブラウザ ベンダーは、MSE を使用する JavaScript ライブラリ(dash.js など)を DASH サポートに残すことを選択しています。JavaScript で DASH を実装すると、ブラウザを更新しなくても適応アルゴリズムを進化させることができます。MSE を使用すると、ブラウザを変更することなく、代替のマニフェスト形式や配信メカニズムを試すことができます。Google の Shaka Player は、EME をサポートする DASH クライアントを実装します。

Mozilla Developer Network の説明では、WebM ツールと FFmpeg を使用して動画をセグメント化し、MPD を作成します。

まとめ

有料の動画や音声を配信するウェブの利用は、急増しています。タブレット、ゲーム機、コネクテッド テレビ、セットトップ ボックスなど、新しいデバイスはすべて、主要なコンテンツ プロバイダのメディアを HTTP 経由でストリーミングできるようです。現在、モバイルとパソコンのブラウザの 85% 以上<video><audio> をサポートしています。Cisco は、2017 年までに世界の消費者のインターネット トラフィックの 80 ~ 90% が動画を利用するようになると予測しています。このような状況では、ブラウザ ベンダーはほとんどのメディア プラグインが依存している API のサポートを縮小するため、保護されたコンテンツの配信に対するブラウザ サポートはますます重要になると考えられます。

関連情報

仕様と基準

記事