CAS 框架

媒体条件接收系统 (Media CAS) 框架提供标准 API,以在一系列数字电视硬件上启用条件接收 (CA) 服务,包括数字有线电视、卫星、地面系统和 IPTV 系统。该框架与Android TV Input 框架Android TV Tuner 框架一起使用,提供从 TV Input Service (TIS) 应用调用的 Java API。

Media CAS的主要目标如下。

  • 提供可供第三方开发者和 OEM 使用的公共 Java API 和原生插件框架,以支持 Android 中广播电视的 CAS。
  • 在 Android 中提供一个 CAS 框架,让 ATV OEM 以一致的方式与各种 CAS 供应商进行互操作。
  • 使用原生插件支持多个第三方 CAS 供应商。 CAS 插件可能使用供应商特定的网络协议、授权管理消息 (EMM)/授权控制消息 (ECM) 格式和解扰器。
  • 支持钥匙梯等硬件安全。
  • 支持 TrustZone 等可信执行环境 (TEE)。

支持的配置

硬件调谐器配置

如果硬件负责 MPEG 传输流解复用和解扰,则调谐器框架向 TIS 应用程序提供条件访问节目特定信息 (PSI) 数据,以便与基于硬件的电视调谐器接口。

条件访问 PSI 数据包括 CA 描述符、ECM 和 EMM。这些结构使 CAS 插件能够获得解密内容流所需的密钥。

硬件调谐器配置图。

图 1.硬件调谐器配置

硬件配置可能有一个 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 数据并使用媒体 CAS Java API 配置 CAS 会话(例如,当应用程序使用自己的 MPEG2-TS 解析器时)。

调谐器配置图。

图 2.使用 MediaExtractor 框架的 IPTV 输入、CAS 和解扰器配置

在软件提取器场景中,无论轨道是否需要安全解码器,提取器都需要为每个加扰轨道提供基于软件或硬件的解扰器对象。这是由于以下原因。

  • 如果轨道不需要安全解码,则提取器对访问单元进行解扰以清除缓冲区并像从清晰的流中一样提取样本。这样MediaCodec就不需要参与解扰了。
  • 如果轨道需要安全解码,提取器可能仍需要解扰器。当传输流在传输数据包级别被加扰时会发生这种情况,其中打包的基本流 (PES) 标头被加扰。提取器需要访问 PES 标头以下游某些信息(例如,表示时间戳)。

    如果传输流在 PES 数据包级别进行了加扰,则提取器不使用解扰器,其中 PES 标头保持清晰。但是,在实际加扰数据包到达之前,无法确认加扰何时发生。为简单起见,如果根据节目映射表 (PMT) 确定轨道被加扰,则假设使用解扰器。

软件配置的限制

当轨道需要安全解码时,解扰器在让解扰操作进入清除缓冲区时需要小心。因为需要不安全的音频解码,所以如果视频解码需要安全解码器,则应该在与音频不同的会话上对其进行加扰。会话的 ECM 必须向插件发出需要安全解码器的信号。

或者,插件必须能够可靠地将密钥绑定到其安全策略。否则,应用程序可以使用音频解扰器轻松获取视频帧。

即使会话需要安全解码器,提取器也可能会要求它输出少量数据以清除缓冲区以处理 PES 标头。为了防止恶意应用程序使插件返回整个访问单元,插件需要解析传输负载以确保负载以适当流类型的 PES 标头开头。否则,插件应该拒绝该请求。

CA 调整序列

调谐到新频道时,TIS 模块注册以接收来自 PSI 调谐器框架的 CA 描述符、ECM 和 EMM。 CA 描述符包含 CA 系统 ID,它唯一标识特定 CA 供应商和其他供应商特定数据。 TIS 查询媒体 CAS 以确定是否存在可以处理 CA 描述符的 CAS 插件。

调整 CAS 内容的图表。

图 3.调整 CAS 内容

如果支持 CA 系统 ID,则创建媒体 CAS 的实例,并将来自 CA 描述符的供应商私有数据提供给插件。然后,在 Media CAS 中打开新会话来处理音频和视频流。新打开的会话接收插件的 ECM 和 EMM。

示例 CAS 插件流程

TIS 使用媒体 CAS API 将 ECM 传送到 CAS 插件。 ECM 包含加密的控制字,需要使用来自 EMM 的信息对其进行解密。 CAS 插件根据 CA 描述符中的供应商特定信息(由setPrivateData()方法提供)确定如何为资产获取 EMM。

EMM 可以使用 CA 插件发起的网络请求在内容流中带内或带外传递。 TIS 使用processEMM()方法将任何带内 EMM 传送到 CA 插件。

如果需要网络请求来获取 EMM,则 CA 插件负责与许可证服务器执行网络事务。

CAS 示例图。

图 4.用于 EMM 和 ECM 处理的示例 CAS 插件

当收到 EMM 时,CA 插件对其进行解析以获得加密密钥以解密控制字。可以将加密的 EMM 密钥和加密的控制字加载到密钥阶梯或可信环境中,以执行控制字解密和内容流的后续解扰。

媒体 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);
    
  • 注册一个事件监听器并允许应用程序指定一个使用循环器的处理程序。

    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);
    
  • 关闭媒体 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);