媒体条件访问系统 (Media CAS) 框架提供了标准 API,可在各种数字电视硬件(包括数字有线电视、卫星电视、地面电视系统和 IPTV 系统)上启用条件访问 (CA) 服务。该框架与 Android TV 输入框架和 Android TV Tuner 框架协同工作,提供从电视输入服务 (TIS) 应用调用的 Java API。
Media CAS 的主要目标如下:
- 提供公共 Java API 和原生插件框架,供第三方开发者和 OEM 使用,以支持 Android 广播电视的 CAS。
- 在 Android 中提供一个 CAS 框架,使 ATV OEM 能够以一致的方式与各种 CAS 供应商互操作。
- 支持使用原生插件的多个第三方 CAS 供应商。CAS 插件可能会使用供应商特定的网络协议、授权管理消息 (EMM)/授权控制消息 (ECM) 格式和解扰器。
- 支持硬件安全功能,例如密钥阶梯。
- 支持可信执行环境 (TEE),例如 TrustZone。
支持的配置
硬件 Tuner 配置
如果硬件负责 MPEG 传输流解复用和解扰,则 Tuner 框架会向 TIS 应用提供条件访问节目特定信息 (PSI) 数据,以便与基于硬件的电视调谐器进行交互。
条件访问 PSI 数据包括 CA 描述符、ECM 和 EMM。这些结构使 CAS 插件能够获取解密内容流所需的密钥。
图 1. 硬件 Tuner 配置
硬件配置可能具有 TEE 层(例如 TrustZone),如图 1 所示。如果没有 TEE 层,CAS 客户端插件可以与平台提供的硬件密钥阶梯服务通信。由于这些接口的供应商特定差异,Media CAS 不对其进行标准化。
软件配置
在 Android 11 之前,Media CAS 框架仍然可以用于处理基于软件的内容,例如来自 IP 多播/单播的 IPTV。TIS 应用负责实例化和正确配置 Media CAS Java 对象。
应用可能会使用 MediaExtractor 或其他 MPEG2-TS 解析器来提取 CA 相关 PSI 数据,例如 CA 描述符、ECM 和 EMM。如果应用使用框架 MediaExtractor,则可以将 CAS 会话管理(例如,打开会话和处理 EMM/ECM)委托给框架 MediaExtractor。然后,MediaExtractor 会使用原生 API 直接配置 CAS 会话。
否则,应用负责提取 CA 相关 PSI 数据,并使用 Media CAS Java API 配置 CAS 会话(例如,当应用使用自己的 MPEG2-TS 解析器时)。
图 2. 使用框架 MediaExtractor 的 IPTV 输入、CAS 和解扰器配置
在软件提取器场景中,提取器需要每个加扰轨道的基于软件或硬件的解扰器对象,无论轨道是否需要安全解码器。这是因为:
- 如果轨道不需要安全解码,则提取器会将访问单元解扰到清除缓冲区,并像从清除流中一样提取样本。这样,
MediaCodec
就无需参与解扰。 如果轨道需要安全解码,则提取器可能仍然需要解扰器。当传输流在传输数据包级别加扰时,就会发生这种情况,其中数据包化基本流 (PES) 标头被加扰。提取器需要访问 PES 标头,以便向下游传递某些信息(例如,呈现时间戳)。
如果传输流在 PES 数据包级别加扰(其中 PES 标头保持清除状态),则提取器不使用解扰器。但是,在实际的加扰数据包到达之前,无法确认何时发生加扰。为简单起见,假设如果根据节目映射表 (PMT) 确定轨道被加扰,则使用解扰器。
软件配置的限制
当轨道需要安全解码时,当允许解扰操作进入清除缓冲区时,解扰器需要谨慎。由于需要不安全的音频解码,如果视频解码需要安全解码器,则应将其与音频在不同的会话中加扰。会话的 ECM 必须向插件发出信号,表明需要安全解码器。
或者,插件必须能够可靠地将密钥与其安全策略关联起来。否则,应用可以轻松地通过音频解扰器获取视频帧。
即使会话需要安全解码器,也可能会被要求将少量数据输出到清除缓冲区,以便提取器处理 PES 标头。为了防止恶意应用使插件返回整个访问单元,插件需要解析传输有效负载,以确保有效负载以适当流类型的 PES 标头开头。否则,插件应拒绝该请求。
CA 调谐序列
当调谐到新频道时,TIS 模块会注册以接收来自 PSI Tuner 框架的 CA 描述符、ECM 和 EMM。CA 描述符包含 CA 系统 ID,该 ID 唯一标识特定的 CA 供应商和其他供应商特定的数据。TIS 查询 Media CAS 以确定是否存在可以处理 CA 描述符的 CAS 插件。
图 3. 调谐 CAS 内容
如果支持 CA 系统 ID,则会创建一个 Media CAS 实例,并将来自 CA 描述符的供应商私有数据提供给插件。然后,在 Media CAS 中打开新会话以处理音频和视频流。新打开的会话接收插件的 ECM 和 EMM。
示例 CAS 插件流程
TIS 使用 Media CAS API 将 ECM 传递到 CAS 插件。ECM 包含加密的控制字,需要使用来自 EMM 的信息对其进行解密。CAS 插件根据 CA 描述符中的供应商特定信息(由 setPrivateData()
方法提供)确定如何获取资产的 EMM。
EMM 可以在内容流中带内传递,也可以使用由 CA 插件发起的网络请求带外传递。TIS 使用 processEMM()
方法将任何带内 EMM 传递到 CA 插件。
如果需要网络请求才能获取 EMM,则 CAS 插件负责使用许可证服务器执行网络事务。
图 4. EMM 和 ECM 处理的示例 CAS 插件
收到 EMM 后,CAS 插件会解析它以获取加密密钥,从而解密控制字。加密的 EMM 密钥和加密的控制字可能会加载到密钥阶梯或可信环境中,以执行控制字解密和后续内容流解扰。
Media CAS Java API
Media CAS Java API 包含以下方法:
列出设备上所有可用的 CA 插件。
class MediaCas.PluginDescriptor { public String getName(); public int getSystemId(); } static PluginDescriptor[] enumeratePlugins();
为指定的 CA 系统构造 Media CAS 实例。这意味着 Media CAS 框架可以同时处理多个 CAS 系统。
MediaCas(int CA_system_id); MediaCas(@NonNull Context context, int casSystemId, @Nullable String tvInputServiceSessionId, @PriorityHintUseCaseType int priorityHint);
注册事件监听器,并允许应用指定要使用其 Looper 的处理程序。
interface MediaCas.EventListener { void onEvent(MediaCas, int event, int arg, byte[] data); void onSessionEvent(@NonNull MediaCas mediaCas, @NonNull Session session, int event, int arg, @Nullable byte[] data); void onPluginStatusUpdate(@NonNull MediaCas mediaCas, @PluginStatus int status, int arg); void onResourceLost(@NonNull MediaCas mediaCas); } void setEventListener(MediaCas.EventListener listener, Handler handler);
发送 CA 系统的私有数据。私有数据可以来自 CA 描述符、条件访问表或带外源。这与特定会话无关。
void setPrivateData(@NonNull byte[] data);
处理 EMM 数据包。
void processEmm(@NonNull byte[] data, int offset, int length);
向 CA 系统发送事件。事件的格式特定于方案,并且对框架是不透明的。
void sendEvent(int event, int arg, @Nullable byte[] data);
为 CA 系统启动指定类型的配置操作。当设备首次注册付费电视服务时,需要先向 CAS 服务器配置。为设备提供一组相关的参数以进行配置。
void provision(String provisionString);
触发授权刷新。当用户订阅新频道(例如,通过响应广告或在电子节目指南 (EPG) 上添加频道)时,应用应该能够告知 CA 客户端刷新授权密钥。
void refreshEntitlements(int refreshType);
关闭 Media CAS 对象。
void close();
打开会话。
Session openSession(); Session openSession(@SessionUsage int sessionUsage, @ScramblingMode int scramblingMode);
关闭先前打开的会话。
void Session#close();
将来自 PMT 中 CA 描述符的 CA 私有数据(可以来自节目信息或 ES 信息部分)提供给 CAS 会话。
void Session#setPrivateData(@NonNull byte[] sessionId, @NonNull byte[] data);
处理会话的 ECM 数据包。
void Session#processEcm(@NonNull byte[] data, int offset, int length);
获取会话 ID。
byte[] Session#getSessionId();
向 CA 系统发送会话事件。事件的格式特定于方案,并且对框架是不透明的。
void Session#sendSessionEvent(int event, int arg, @Nullable byte[] data);