什么是 EME?

Encrypted Media Extensions 提供了一种 API,可让 Web 应用与内容保护系统进行交互,以允许播放加密的音频和视频。

EME 旨在让同一应用和加密文件能够在任何浏览器中使用,而无论底层保护系统为何。前者可以通过标准化 API 和流程实现,而后一种则通过通用加密的概念实现。

EME 是对 HTMLMediaElement 规范的扩展,因此得名。作为“扩展程序”意味着浏览器对 EME 的支持是可选的:如果浏览器不支持加密媒体,则无法播放加密媒体内容,但 EME 不要求符合 HTML 规范。根据 EME 规范

此方案扩展了 HTMLMediaElement,这些 API 提供了用于控制受保护内容的播放的 API。

该 API 支持各种用例,从简单的清晰密钥解密到高价值视频(假设有适当的用户代理实现)等各种用例,不一而足。许可/密钥交换由应用控制,这有助于开发支持一系列内容解密和保护技术的强大播放应用。

本规范并未定义内容保护或数字版权管理系统。相反,它定义了一个通用 API,可用于发现、选择此类系统以及较简单的内容加密系统并与之互动。为遵守本规范,无需实现数字版权管理:仅将清除密钥系统实现为通用基准。

该通用 API 支持一组简单的内容加密功能,可将身份验证和授权等应用功能留给网页作者。其实现方式是要求网页通过内容保护系统专用消息进行中介,而不是假定加密系统与许可或其他服务器之间进行带外通信。

EME 实现使用以下外部组件:

  • 密钥系统:内容保护 (DRM) 机制。除 Clear Key 之外(下文对此进行了详细说明),EME 不会定义密钥系统本身。
  • 内容解密模块 (CDM):支持播放加密媒体的客户端软件或硬件机制。与密钥系统一样,EMME 不定义任何 CDM,但提供一个接口,供应用与可用 CDM 进行交互。
  • 许可(密钥)服务器:与 CDM 交互,提供用于解密媒体的密钥。应用负责与许可服务器进行协商。
  • 包装服务:对媒体进行编码和加密,以便分发/消费。

请注意,使用 EME 的应用会与许可服务器交互以获取密钥以启用解密,但用户身份和身份验证不是 EME 的一部分。在用户进行身份验证后(可选)会检索密钥以启用媒体播放。Netflix 等服务必须在其 Web 应用内对用户进行身份验证:当用户登录应用时,应用确定用户的身份和权限。

EME 如何运作?

以下是 EME 组件的交互方式,与以下代码示例相对应:

如果有多种格式或编解码器可用,MediaSource.isTypeSupported()HTMLMediaElement.canPlayType() 均可用于选择正确的格式或编解码器。 但是,CDM 可能仅支持浏览器支持的部分未加密内容。最好在选择格式和编解码器之前协商 MediaKeys 配置。如果应用等待加密事件,但 MediaKeys 显示无法处理所选格式/编解码器,那么在不中断播放的情况下切换可能太晚了。

建议的流程是先协商 MediaKey,然后使用 MediaKeysSystemAccess.getConfiguration() 找出商定的配置。

如果只有一种格式/编解码器可供选择,则无需使用 getConfiguration()。不过,最好还是先设置 MediaKeys。 等待加密事件的唯一原因是无法确定内容是否已加密,但实际上不太可能。

  1. Web 应用尝试播放具有一个或多个加密流的音频或视频。
  2. 浏览器识别出媒体已加密(请参见下方方框了解具体过程),并使用从媒体中获取的加密相关元数据 (initData) 触发加密事件。
  3. 应用处理加密事件:

    1. 如果没有任何 MediaKeys 对象与媒体元素相关联,请先使用 navigator.requestMediaKeySystemAccess() 以检查哪些键系统可用,选择一个可用的键系统,然后通过 MediaKeySystemAccess 对象为可用的键系统创建 MediaKeys 对象。请注意,MediaKeys 对象的初始化应在第一个加密事件之前进行。获取许可服务器网址由应用完成,与选择可用的密钥系统无关。MediaKeys 对象表示可用于对音频或视频元素的媒体进行解密的所有密钥。它代表 CDM 实例,并提供对 CDM 的访问权限,尤其是用于创建用于从许可服务器获取密钥的密钥会话。

    2. 创建 MediaKeys 对象后,将其分配给媒体元素:setMediaKeys() 会将 MediaKeys 对象与 HTMLMediaElement 相关联,以便在播放期间(即解码期间)使用其键。

  4. 应用通过对 MediaKeys 调用 createSession() 来创建 MediaKeySession。这将创建一个 MediaKeySession,它表示许可及其密钥的生命周期。

  5. 应用通过对 MediaKeySession 调用 generateRequest(),将从加密处理程序中获取的媒体数据传递给 CDM,从而生成许可请求。

  6. CDM 触发消息事件:即从许可服务器获取密钥的请求。

  7. MediaKeySession 对象接收消息事件,而应用向许可服务器发送消息(例如通过 XHR)。

  8. 应用收到来自许可服务器的响应,并使用 MediaKeySession 的 update() 方法将数据传递给 CDM。

  9. CDM 使用许可中的密钥解密媒体。可以在与媒体元素关联的 MediaKey 内的任何会话中使用有效键。CDM 将访问密钥和政策(按密钥 ID 编入索引)。

媒体播放将继续。

浏览器如何得知媒体已加密?

这些信息在媒体容器文件的元数据中,其格式为 ISO BMFF 或 WebM。对于 ISO BMFF,这意味着标头元数据,称为保护方案信息框。WebM 使用 Matroska ContentEncryption 元素,并添加了一些 WebM 专用功能。EME 专属注册表中为每个容器提供了相关指南。

请注意,CDM 和许可服务器之间可能会有多条消息,并且此过程中的所有通信对浏览器和应用都是不透明的:虽然消息只能由 CDM 和许可服务器理解,但应用层可以查看 CDM 发送的消息类型。许可请求包含 CDM 有效性(和信任关系)的证明,以及对生成许可中的内容密钥进行加密时要使用的密钥。

但 CDM 实际上是做什么的呢?

EME 实现本身并不提供解密媒体的方法:它只是为 Web 应用提供一个 API,以便与内容解密模块交互。

CDM 的实际用途并未由 EME 规范定义,CDM 可以同时处理媒体的解码(解压缩)和解密。CDM 功能有以下几种可能的选择,从最低到最稳健:

  • 仅限解密,支持使用常规媒体管道(例如,通过 <video> 元素)播放。
  • 解密和解码,将视频帧传递给浏览器进行渲染。
  • 解密和解码,直接在硬件(例如 GPU)中渲染。

可通过多种方式向 Web 应用启用 CDM:

  • 将 CDM 与浏览器捆绑在一起。
  • 单独分配 CDM。
  • 将 CDM 内置到操作系统中。
  • 在固件中包含 CDM。
  • 在硬件中嵌入 CDM。

EME 规范未定义 CDM 提供的方式,但在所有情况下,浏览器都负责审核和公开 CDM。

EME 不强制要求使用特定的密钥系统;在当前的桌面和移动浏览器中,Chrome 支持 Widevine,IE11 支持 PlayReady。

从许可服务器获取密钥

在典型的商业用途中,内容将使用打包服务或工具进行加密和编码。加密媒体可供在线使用后,Web 客户端可以从许可服务器获取密钥(包含在许可内),并使用该密钥来解密和播放内容。

以下代码(改写自规范示例)显示了应用如何选择适当的密钥系统并从许可服务器获取密钥。

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

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

    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。例如,使用从 Widevine 许可服务器获取密钥的 Widevine CDM,使用 Playready 打包的视频可以在浏览器中播放。

这与仅支持完整垂直堆栈(包括通常还包含应用运行时的单个客户端)的旧解决方案形成鲜明对比。

通用加密 (CENC) 是一种 ISO 标准,用于定义 ISO BMFF 的保护方案;类似的概念也适用于 WebM。

清除密钥

虽然 EME 未定义 DRM 功能,但该规范目前规定,所有支持 EME 的浏览器都必须实现 Clear Key。使用此系统,可以使用密钥加密媒体,然后只需提供该密钥即可进行播放。Clear Key 可以内置在浏览器中:它不需要使用单独的解密模块。

虽然不太可能用于许多类型的商业内容,但 Clear Key 可以在支持 EME 的所有浏览器中完全实现互操作。这也非常便于测试 EME 实现和使用 EME 的应用,而无需从许可服务器请求内容密钥。simpl.info/ck 上有一个简单的 Clear Key 示例。下面是代码演示(与上述步骤类似),但不与许可服务器交互。

// 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),并且正在开发其他解决方案。

HTMLMediaElement 是一种体现简单之美的生物。

我们只需提供一个 src 网址,即可加载、解码和播放媒体:

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

Media Source API 是对 HTMLMediaElement 的扩展,可让 JavaScript 从视频“块”构建要播放的流,从而实现对媒体来源的更精细控制。进而支持自适应流式传输和时移等技术。

为什么 MSE 对 EME 很重要?因为除了分发受保护内容之外,商业内容提供商还必须根据网络条件和其他要求调整内容分发方式。例如,Netflix 会随着网络状况的变化动态改变视频流比特率。EME 支持播放 MSE 实现提供的媒体流,就像处理通过 src 属性提供的媒体一样。

如何对以不同比特率编码的媒体进行分块和播放?请参阅下面的 DASH 部分

您可以在 simpl.info/mse 中查看 MSE 的实际运用;在本示例中,我们使用 File API 将一个 WebM 视频拆分为五个分块。在生产应用中,视频块将通过 AJAX 进行检索。

首先,创建 SourceBuffer:

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

然后,通过使用附加缓冲区()方法附加每个分块,将整部电影“流式传输”到一个视频元素:

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 入门指南,详细了解 MSE。

多设备、多平台、移动设备 - 无论您怎么称呼它,通常在多变的连接条件下,用户都能畅享网络体验。动态的自适应交付对于应对多设备环境中的带宽限制和变化至关重要。

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 提供测试视频流

此媒体以不同的比特率进行多次编码。每种编码都称为一种表示法。这些媒体细分会拆分为多个媒体细分。客户端通过 HTTP 从表示法中按顺序请求片段,以播放节目。可将表示形式分组为包含等效内容的表示形式的自适应集。如果客户端想要更改比特率,则可以从当前适配集中选择替代项,并开始从该表示法中请求片段。内容的编码方式是为了便于客户端轻松进行这种切换。除了许多媒体片段之外,表示法通常还包含初始化片段。这可以被视为标头,其中包含有关编码、帧大小等的信息。客户端需要针对给定表示法获取此标头,然后才能使用来自该表示法的媒体段。

总结:

  1. 媒体以不同的比特率编码。
  2. 您可以从 HTTP 服务器获取不同比特率的文件。
  3. 客户端 Web 应用选择要使用 DASH 检索和播放的比特率。

在视频分割过程中,系统会以编程方式构建称为媒体呈现描述 (MPD) 的 XML 清单。下面介绍了自适应集和表示法,包含时长和网址。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。不过,为了给 Web 开发者提供更大的灵活性,浏览器供应商改为选择让 DASH 支持由使用 MSE 的 JavaScript 库(例如 dash.js)完成。在 JavaScript 中实现 DASH 可让自适应算法不断改进,而无需浏览器更新。使用 MSE 还有助于试验替代的清单格式和分发机制,而无需更改浏览器。Google 的 Shaka Player 实现了支持 EME 的 DASH 客户端。

Mozilla 开发者网络包含关于如何使用 WebM 工具和 FFmpeg 片段和构建 MPD 的说明

总结

使用网络提供付费视频和音频服务正迅速增加。似乎每台新设备(无论是平板电脑、游戏机、联网电视还是机顶盒)都能够通过 HTTP 流式传输来自主要 content provider 的媒体。超过 85% 的移动设备和桌面浏览器现在支持 <video> 和 <audio>,根据 Cisco 的估算,到 2017 年,视频将占全球消费者互联网流量的 80% 到 90%。在这种情况下,浏览器对受保护内容分发的支持可能会变得越来越重要,因为浏览器供应商减少对大多数媒体插件所依赖的 API 的支持

补充阅读材料

规范和标准

EME 规范:最新的编辑器草稿 通用加密 (CENC) 媒体来源扩展:最新的编辑器草稿 DASH 标准(是的,它是 PDF 格式) DASH 标准概览

文章

DTG 在线讲座(部分过时) 什么是 EME?,作者:Henri Sivonen 媒体源扩展入门指南 MPEG-DASH Test Streams:BBC R&D 博文

样本歌曲

清除密钥演示:simpl.info/ck 媒体来源扩展 (MSE) 演示 Google 的 Shaka 播放器实现了支持 EME 的 DASH 客户端

反馈