Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

EME WTF? 加密媒体扩展介绍 #26

Open
gnipbao opened this issue Jun 8, 2018 · 0 comments
Open

EME WTF? 加密媒体扩展介绍 #26

gnipbao opened this issue Jun 8, 2018 · 0 comments

Comments

@gnipbao
Copy link
Owner

gnipbao commented Jun 8, 2018

原文链接: EME WTF?
翻译日期: 2018年6月8日
翻译人员: Gnip

目录

加密媒体扩展提供了一个API,允许web应用与内容保护系统交互,允许播放加密的音频和视频。

EME被设计来保证相同的应用和加密文件可以在任何浏览器环境使用,不管底层保护系统。
前者是通过标准API和规则实现,而后者是由通用加密Common Encryption概念实现。

EME得名来自对HTMLMediaElement规范的扩展。
作为一个“扩展”意味着浏览器支持EME:如果浏览器不支持加密媒体,它将无法播放加密媒体,但EME对于HTML规范的依赖不是必须需的。
从EME的规范来看:

这个提议扩展HTMLMediaElement提供api来控制播放受保护的内容。

API支持从简单的密钥解密到高价值的视频(给出一个适当的用户代理实现)的情况。
许可/密钥交换是由应用程序控制,促进开发健壮的播放应用程序支持一系列内容解密和保护技术。

本规范没有定义内容保护或数字版权管理系统。相反,它定义了一个通用的API,可以用来发现、选择或者与这些系统以及简单的内容加密系统交互。数字版权管理的实现不需要遵守此规范:只有Clear Key系统需要实现为一个共同的基准。

通用的API支持一组简单的内容加密功能,而把一些应用程序的功能,比如身份验证和授权留给页面作者。这是通过获取由页面分发的内容保护系统的特的消息而不是假设带外之间的通信加密系统或者许可证或其他服务器的通信。

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

  • Key System: 内容保护(DRM)机制。EME不定义key system本身,除了clear key(下面详细说明)。

  • Content Decryption Module (CDM): 客户端软件或硬件机制,来保证播放加密媒体。
    和key system一样,EME不定义任何CDMs,但提供了一个接口与CDMs应用程序进行交互。

  • License (Key) server: 与CDM进行交互,提供密钥解密媒体。与许可服务器交涉是主要责任。

  • Packaging service: 编码和加密媒体分布/消费

注意应用程序使用EME与一个许可证服务器交互获取密钥来解密,但用户标识和身份验证并不是EME的一部分。
检索键,使媒体播放(可选)之后对用户进行身份验证。
这种服务,例如Netflix必须验证用户在他们的web应用程序:当用户登录应用程序,应用程序决定了用户的身份和特权。

EME如何工作?

eme
EME组件如何交互的,对应下面的代码示例:

如果多种格式和编解码器都是支持的,MediaSource.isTypeSupported(),或HTMLMediaElement.canPlayType()都可以用来选择正确的一个。然而,CDM可能只支持浏览器支持加密的内容的一个子集。最好是在选择一个格式和编解码器之前,使用一个MediaKeys配置。如果应用程序等待加密事件后,然而 MediaKeys显示它无法处理所选的格式/编解码器,它可能是来不及切换而不得不中断播放。

推荐的流程是先通过MediaKeys,然后使用MediaKeysSystemAccess.getConfiguration() 获取可靠的配置。

如果只有一个格式/编码器去选择,然后就没有必要
getConfiguration(). 然而,最好是先建立MediaKeys。等待加密事件的唯一理由是如果没有办法知道内容是否加密,但实际上这是不可能的。

  1. 一个web应用程序试图播放有一个或多个加密流音频或视频。
  2. 浏览器认出媒体是加密的(见下面如何发生),然后会通过从媒体获得的加密元数据即(initData)触发一个加密的事件。
  3. 应用程序处理加密事件:
    • 如果没有MediaKeys对象与媒体元素关联,首先选择一个可用的密钥系统通过使用navigator.requestMediaKeySystemAccess()检查哪些系统可用,然后通过MediaKeySystemAccess为密钥系统创建为一个可用的MediaKeys对象。注意,MediaKeys对象的初始化应该在第一个加密事件之前。通过选择一个可用的密钥系统,获得许可证服务器的URL是一个独立应用程序。MediaKeys对象代表了所有可用的密钥来解密音频或视频的媒体元素。它代表了CDM实例并提供访问CDM,专门用于创建密钥会话,用于获取密钥从许可证书服务器。
    • 一旦MediaKeys对象被创建,将其分配给媒体元素:setMediaKeys()分配给HTMLMediaElement元素,所以可以使用播放期间使用密钥,例如解码的时候。
  4. 应用程序建立MediaKeySession通过调用MediaKeys上的createSession()方法。MediaKeySession代表了证书和它密钥的生命周期。
  5. 应用程序通过将加密处理中获取的媒体数据传递给CDMl来生成许可证请求。通过MediaKeySession调用generateRequest()方法。
  6. CDM出发一个消息事件:从证书服务器获取密钥的请求。
  7. MediaKeySession 对象接收到消息事件然后应用程序通过(例如xhr)发送消息到证书服务器。
  8. 应用程序接收到响应从证书服务器并且传递数据到CDM使用MediaKeySessionupdate()方法。
  9. CDM解密媒体使用证书中的密钥。一个有效的密钥可能被使用在MediaKeys关联的媒体元素任何会话中。CDM会访问有密钥id索引的密钥和策略。
  10. 媒体播放恢复。

浏览器如何知道媒体是加密的?

此信息位于媒体容器文件的元数据中,该文件将采用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中嵌入硬件。

CDM如何提供并不是由EME规范定义的,但在所有情况下,浏览器负责审核和公开CDM。

EME没有要求特定的关键系统; 在当前的桌面和移动浏览器中,Chrome支持Widevine,IE11支持PlayReady。

从许可证服务器获取密钥

可在线使用,Web客户端就可以从许可证服务器获取密钥(包含在许可证中),并使用该密钥来启用内容的解密和播放。

以下代码(根据规范示例进行了调整)显示了应用程序如何选择适当的密钥系统并从许可证服务器获取密钥。

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许可证服务器获取密钥。

这与传统的解决方案形成鲜明对比,传统解决方案只能使用完整的垂直堆栈,包括通常还包含应用程序运行时的单个客户端。

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

清除密钥

尽管EME没有定义DRM功能,但该规范目前要求所有支持EME的浏览器必须实现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完成对Clear Key使用的视频加密。商业服务也是可用的(至少对于ISO BMFF / MP4)并且正在开发其他解决方案。

相关技术#1:

媒体源扩展(MSE)

HTMLMediaElement是简约和美好的。

我们可以简单地通过提供一个src URL来加载,解码和播放媒体:

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

The 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"');

然后通过使用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);
  }
};

HTML5 Rocks文章中了解有关MSE的更多信息。

相关技术#2:

基于HTTP的动态自适应流媒体(DASH)

多设备,多平台,移动 - 无论您怎么称呼它,Web都经常在可变连接条件下体验。动态的自适应交付对于应对多设备领域的带宽限制和可变性至关重要。

DASH(也称为MPEG-DASH)旨在在片状世界中实现尽可能最佳的媒体传输,以实现流媒体和下载。其他一些技术可以做类似的事情 - 例如Apple的HTTP实时流媒体(HLS)和微软的平滑流媒体 - 但DASH是通过基于开放标准的HTTP进行自适应比特率流传输的唯一方法。DASH已被YouTube等网站使用。

这与EME和MSE有什么关系?基于MSE的DASH实现可以解析manifest,以适当的比特率下载视频片段,并在饥饿时将它们提供给视频元素 - 使用现有的HTTP基础架构。

换句话说,DASH使商业内容提供商能够对受保护内容进行自适应流式传输。

DASH做什么:

  • 动态: 响应变化的条件。
  • 自适应: 适应提供适当的音频或视频比特率。
  • 流媒体: 允许流媒体以及下载。
  • HTTP: 利用HTTP的优势实现内容交付,而没有传统流媒体服务器的缺点。

BBC已经开始使用DASH提供测试流

媒体以不同比特率编码多次。每种编码称为表示。这些被分成许多媒体分部。客户端通过从HTTP请求中按顺序请求分​​段来播放程序。表示可以分组为包含等同内容的表示的适应集。如果客户希望改变比特率,它可以从当前适配集合中选择一种替代方案,并开始从该表示中请求分段。内容以这种方式进行编码,以便客户端可以轻松地进行切换。除了一些媒体分部之外,一个表示通常还有一个初始化分段。这可以被认为是一个标题,包含关于编码,帧大小等的信息。

总结:

  1. 媒体以不同的比特率进行编码。
  2. 不同的比特率文件可从HTTP服务器获得。
  3. 客户端网络应用程序选择要使用DASH检索和回放的比特率。

作为视频分割过程的一部分,以编程方式构建称为媒体演示描述(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(例如dash.js)将DASH支持留给JavaScript库。在JavaScript中实现DASH允许自适应算法在不需要浏览器更新的情况下发展。使用MSE还可以实现替代清单格式和传送机制,而无需更改浏览器。Google的Shaka Player实现了一个支持EME的DASH客户端。

Mozilla开发者网络有关于如何使用WebM工具和FFmpeg来分割视频和构建MPD的说明。

结论

利用网络提供付费视频和音频的速度正在加快。看起来,每个新设备,无论是平板电脑,游戏机,连接电视还是机顶盒,都能够通过HTTP从主要内容提供商流媒体。目前,超过85%的移动和桌面浏览器支持

进一步阅读

规格和标准

参考文章

Demos

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

1 participant