CAS 框架

媒体条件访问系统 (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 插件能够获取解密内容流所需的密钥。

Diagram of the hardware Tuner configuration.

图 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 解析器时)。

Diagram of the Tuner configuration.

图 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 插件。

Diagram of tuning CAS content.

图 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 插件负责使用许可证服务器执行网络事务。

Diagram of an example 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);