Encrypted Media Extensions 简介
Encrypted Media Extensions 提供了一种 API,可让 Web 应用与内容保护系统进行交互,以允许播放加密的音频和视频。
EME 的设计宗旨是在任何浏览器中使用相同的应用和加密文件,而无论底层保护系统为何。前者可以通过标准化 API 和流程实现,而后一种则通过通用加密的概念实现。
EME 是对 HTMLMediaElement
规范的扩展,因此得名。作为“扩展程序”意味着浏览器对 EME 的支持是可选的:如果浏览器不支持加密媒体,则无法播放加密媒体,但 EME 不要求符合 HTML 规范。根据 EME 规范:
EME 实现使用以下外部组件:
- 密钥系统:内容保护 (DRM) 机制。除 Clear Key 之外,EME 本身并未定义密钥系统(详见下文)。
- 内容解密模块 (CDM):一种客户端软件或硬件机制,支持播放加密媒体。与 Key Systems 一样,EMME 不定义任何 CDM,但是提供一个接口,供应用与可用的 CDM 进行交互。
- 许可(密钥)服务器:与 CDM 交互,提供用于解密媒体的密钥。应用负责与许可服务器进行协商。
- 包装服务:对媒体进行编码和加密,以便分发/消费。
请注意,使用 EME 的应用会与许可服务器交互以获取密钥以启用解密,但用户身份和身份验证不是 EME 的一部分。在验证用户身份后(可选)检索密钥以启用媒体播放。Netflix 等服务必须在其 Web 应用内对用户进行身份验证:当用户登录应用时,应用确定用户的身份和权限。
EME 如何运作?
以下是 EME 组件的交互方式,与以下代码示例相对应:
- Web 应用尝试播放具有一个或多个加密流的音频或视频。
- 浏览器识别出媒体已加密(请参见下面的方框了解具体过程),并触发
encrypted
事件,以及从媒体中获取的加密相关元数据 (initData
)。 - 应用处理
encrypted
事件:- 如果没有与媒体元素相关联的
MediaKeys
对象,请先使用navigator.requestMediaKeySystemAccess()
选择一个可用的按键系统,以检查可用的按键系统,然后通过MediaKeySystemAccess
对象为可用的按键系统创建一个MediaKeys
对象。请注意,MediaKeys 对象的初始化应在第一个encrypted
事件之前进行。获取许可服务器网址由应用独立于选择可用的密钥系统来完成。MediaKeys
对象表示可用于解密音频或视频元素的媒体的所有键。它代表 CDM 实例,并提供对 CDM 的访问权限,特别是用于创建用于从许可服务器获取密钥的密钥会话。 - 创建
MediaKeys
对象后,将其分配给媒体元素:setMediaKeys()
会将MediaKeys
对象与 HTMLMediaElement 相关联,以便在播放期间(即解码期间)使用其键。
- 如果没有与媒体元素相关联的
- 应用通过对
MediaKeys
调用createSession()
来创建MediaKeySession
。这将创建一个MediaKeySession
,用于表示许可及其密钥的生命周期。 - 应用通过对
MediaKeySession
调用generateRequest()
,将encrypted
处理程序中获取的媒体数据传递给 CDM,从而生成许可请求。 - CDM 触发
message
事件:即从许可服务器获取密钥的请求。 MediaKeySession
对象收到message
事件,然后应用向许可服务器发送消息(例如通过 XHR)。- 应用收到来自许可服务器的响应,并使用
MediaKeySession
的update()
方法将数据传递给 CDM。 - CDM 使用许可中的密钥解密媒体。可以在与媒体元素关联的
MediaKey
内的任何会话中使用有效键。CDM 将访问密钥和政策(按密钥 ID 编入索引)。 - 媒体播放将继续。
哎呀...
请注意,CDM 和许可服务器之间可能会有多条消息,并且此流程中的所有通信对浏览器和应用都是不透明的:虽然应用层可以查看 CDM 发送的消息类型,但消息仅可由 CDM 和许可服务器识别。许可请求包含 CDM 有效性(和信任关系)的证明,以及对所生成许可中的内容密钥进行加密时要使用的密钥。
...但 CDM 实际上是做什么的?
EME 实现本身并不提供解密媒体的方法:它只是提供一个 API,供 Web 应用与内容解密模块交互。
EME 规范实际上没有定义 CDM 的具体功能,CDM 可以处理媒体的解码(解压缩)和解密。CDM 功能有以下几种可能的选择,从最不完善到最可靠:
- 仅限解密,支持使用常规媒体管道进行播放,例如通过
<video>
元素。 - 解密和解码,将视频帧传递给浏览器进行渲染。
- 解密和解码,直接在硬件(例如 GPU)中渲染。
可通过多种方式向 Web 应用启用 CDM:
- 将 CDM 与浏览器捆绑在一起。
- 单独分配 CDM。
- 将 CDM 内置到操作系统中。
- 在固件中包含 CDM。
- 在硬件中嵌入 CDM。
EME 规范未对 CDM 提供的方式进行定义,但在所有情况下,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。例如,使用从 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,您可以按照 webm_crypt 说明对视频进行加密,以便与清除密钥配合使用。我们还提供了商业服务(至少适用于 ISO BMFF/MP4),并且正在开发其他解决方案。
相关技术 1
媒体来源扩展 (MSE)
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,请参阅 HTML5 Rocks 一文。
相关技术 2
基于 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 提供测试视频流:
总结:
- 媒体以不同的比特率编码。
- 您可以从 HTTP 服务器获取不同比特率的文件。
- 客户端 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 播放器实现了支持 EME 的 DASH 客户端。
Mozilla 开发者网络提供了有关如何使用 WebM 工具和 FFmpeg 片段和制作 MPD 的说明。
总结
越来越多的用户使用网络提供付费视频和音频内容,并且这个数字飞速增长。似乎每台新设备(无论是平板电脑、游戏机、联网电视还是机顶盒)都能够通过 HTTP 流式传输来自主流内容提供商的媒体。超过 85% 的移动设备和桌面浏览器现在支持 <video>
和 <audio>
。根据 Cisco 估计,到 2017 年,视频将占全球消费者互联网流量的 80% 至 90%。在这种情况下,浏览器对受保护内容分发的支持可能会变得越来越重要,因为浏览器供应商会减少对大多数媒体插件所依赖的 API 的支持。
补充阅读材料
规范和标准
- EME 规范:最新编辑的草稿<
- 通用加密 (CENC)
- 媒体来源附加信息
- DASH 标准(是的,它是 PDF 格式)
- DASH 标准概览
文章
- DTG 在线讲座(部分已过时)
- 什么是 EME?,作者:Henri Sivonen
- “HTML5 Rocks Media Source Extensions”一文
- MPEG-DASH 测试流:BBC R&D 博文